﻿using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using GodLesZ.Library;
using GodLesZ.Library.Diagnostics;
using GodLesZ.Library.Network;
using GodLesZ.Library.Timer;
using Grove.Server.Network;
using Timer = GodLesZ.Library.Timer.Timer;

namespace Grove.Server {

	public delegate void Slice();

	public class Core {
		protected static Core mInstance;

		public static Core Instance {
			get { return mInstance ?? (mInstance = new Core()); }
		}

		private bool mClosing;
		private bool mCrashed;

		private SocketConnector mSocketConnector;
		private Thread mThread;
		private Thread mTimerThread;
		private Process mProcess;
		private Assembly mAssembly;
		private ConsoleEventHandler mConsoleEventHandler;
		private readonly AutoResetEvent mSignal = new AutoResetEvent(true);
		private DateTime mProfileStart;
		private TimeSpan mProfileTime;

		private uint mCycleIndex;
		private readonly float[] mCyclesPerSecond = new float[100];

		public Slice Slice;

		public float CyclesPerSecond {
			get { return mCyclesPerSecond[((mCycleIndex - 1)) % mCyclesPerSecond.Length]; }
		}

		public float AverageCPS {
			get {
				float t = 0.0f;
				int c = 0;

				for (int i = 0; i < mCycleIndex && i < mCyclesPerSecond.Length; ++i) {
					t += mCyclesPerSecond[i];
					++c;
				}

				return (t / Math.Max(c, 1));
			}
		}

		public bool Closing {
			get { return mClosing; }
		}

		public bool Crashed {
			get { return mCrashed; }
		}

		public bool Profiling {
			get { return BaseProfile.Profiling; }
			set {
				if (BaseProfile.Profiling == value) {
					return;
				}

				BaseProfile.Profiling = value;

				if (mProfileStart > DateTime.MinValue) {
					mProfileTime += DateTime.Now - mProfileStart;
				}

				mProfileStart = (BaseProfile.Profiling ? DateTime.Now : DateTime.MinValue);
			}
		}

		public TimeSpan ProfileTime {
			get {
				if (mProfileStart > DateTime.MinValue) {
					return mProfileTime + (DateTime.Now - mProfileStart);
				}

				return mProfileTime;
			}
		}

		public Thread Thread {
			get { return mThread; }
		}

		public Process Process {
			get { return mProcess; }
		}

		public Assembly Assembly {
			get { return mAssembly; }
		}

		public SocketConnector Connector {
			get { return mSocketConnector; }
		}

		[DllImport("Kernel32")]
		private static extern bool SetConsoleCtrlHandler(ConsoleEventHandler callback, bool add);

		public Core()
			: this(new string[] { }) {

		}

		public Core(string[] args) {
#if !DEBUG
			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( CurrentDomain_UnhandledException );
#endif
			AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;

			ParseArguments(args);

			try {
				mThread = Thread.CurrentThread;
				mProcess = Process.GetCurrentProcess();
				mAssembly = Assembly.GetEntryAssembly();
				if (mThread != null) {
					mThread.Name = "Core Thread";
				}

				var ttObj = new TimerThread();
				mTimerThread = new Thread(ttObj.TimerMain);
				mTimerThread.Name = "Timer Thread";

				int width = Math.Min(100, Console.LargestWindowWidth - 2);
				Console.CursorVisible = false;
				Console.Clear();
				Console.WindowLeft = Console.WindowTop = 0;
				if (Console.WindowWidth < width) {
					Console.WindowWidth = width;
				}

				var logo = new LogoPrinter("(", EConsoleColor.Blue, ")", EConsoleColor.Blue);
				logo.Print();

				Version ver = mAssembly.GetName().Version;
				CConsole.StatusLine("Grove.Server - Version {0}.{1}, Build {2}", ver.Major, ver.Minor, ver.Build);

				mConsoleEventHandler = OnConsoleEvent;
				SetConsoleCtrlHandler(mConsoleEventHandler, true);

				SocketPool.Create();

				string ip = "localhost";
				int port = 6000;
				mSocketConnector = new SocketConnector(ip, port);
				PacketHandlers.Initialize();

			} catch (Exception e) {
				CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true));
			}
		}


		public void Run() {

			try {
				// Start Timer Thread
				mTimerThread.Start();

				// Start netstate message queue
				NetState.Initialize();

				// Invoke serverStarted event
				Events.InvokeServerStarted();

				// Some stuff for diagnostics
				DateTime last = DateTime.Now;
				const int sampleInterval = 100;
				const float ticksPerSecond = (float)(TimeSpan.TicksPerSecond * sampleInterval);
				int sample = 0;

				// Run loop
				while (mSignal.WaitOne()) {
					// Got a single - check which

					// Timer updates
					Timer.Slice();
					// Socket updates
					mSocketConnector.Slice();
					// Netstate updates
					NetState.FlushAll();
					// Kill diposed netstates
					NetState.ProcessDisposedQueue();

					// Call slice event
					if (Slice != null) {
						Slice();
					}

					// Catch cycles per second for diagnostics
					if ((++sample % sampleInterval) == 0) {
						DateTime now = DateTime.Now;
						mCyclesPerSecond[mCycleIndex++ % mCyclesPerSecond.Length] = ticksPerSecond / (now.Ticks - last.Ticks);
						last = now;
					}
				}

			} catch (Exception e) {
				CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true));
			}

		}


		private void ParseArguments(string[] args) {
			// Enable ip and port from command line 
			if (args.Length == 0) {
				return;
			}

			for (var i = 0; i < args.Length; i++) {
				if (args[i] == "ip") {

					i++;
					continue;
				}
				if (args[i] == "p") {

					i++;
					continue;
				}
			}
		}


		private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
			var ex = e.ExceptionObject as Exception;
			CConsole.Write(EConsoleColor.Error, e.IsTerminating ? "Error: " : "Warning: ");
			if (ex != null) {
				CConsole.WriteLine(EConsoleColor.Error, ex.Message);
				if (ex.StackTrace != string.Empty) {
					CConsole.WriteLine(ex.StackTrace);
				}
			}

			if (e.IsTerminating) {
				mCrashed = true;
				var close = false;
				try {
					var args = new CrashedEventArgs(e.ExceptionObject as Exception);
					Events.InvokeCrashed(args);
					close = args.Close;
				} catch { }

				if (!close) {
					SocketPool.Destroy();

					CConsole.ErrorLine("This exception is fatal, press return to exit");
					CConsole.Read();
				}

				mClosing = true;
			}
		}

		private void CurrentDomain_ProcessExit(object sender, EventArgs e) {
			HandleClosed();
		}

		public void Kill() {
			Kill(false);
		}

		public void Kill(bool restart) {
			HandleClosed();

			if (restart) {
				Process.Start(mAssembly.Location);
			}

			mProcess.Kill();
		}

		private void HandleClosed() {
			if (mClosing) {
				return;
			}

			if (!mCrashed) {
				Events.InvokeShutdown(new ShutdownEventArgs());
			}

			mClosing = true;
			CConsole.StatusLine("Exiting...");

			// do some kills
			SocketPool.Destroy();

			CConsole.StatusLine("Exiting finished! Press any Key to close.");
		}

		private bool OnConsoleEvent(ConsoleEventType type) {
			if (type == ConsoleEventType.CtrlLogoffEvent) {
				return true;
			}

			Kill();

			return true;
		}

		public void Set() {
			mSignal.Set();
		}

		private delegate bool ConsoleEventHandler(ConsoleEventType type);

		private enum ConsoleEventType {
			CtrlLogoffEvent = 5
		}

	}

}